package com.zzyl.job;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.enums.AlertDataType;
import com.zzyl.enums.LocationType;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserMapper;
import com.zzyl.vo.DeviceDataVo;
import com.zzyl.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Component
@Slf4j
public class DeviceAlertJob {

    @Autowired
    private AlertRuleMapper alertRuleMapper;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private UserMapper userMapper;
    @Value("${zzyl.alert.deviceMaintainerRole}")
    private String deviceMaintainerRole;
    @Value("${zzyl.alert.managerRole}")
    private String managerRole;
    @Autowired
    private AlertDataMapper alertDataMapper;
    @Autowired
    private WebSocketServer webSocketServer;

    @Scheduled(cron = "0 0/1 * * * ?")
    public void deviceAlert() {
        //1、查询所有的报警规则，如果为空直接返回
        log.info("=========> 1、查询所有的报警规则");
        List<AlertRule> ruleList = alertRuleMapper.selectAll();
        if (CollUtil.isEmpty(ruleList)) {
            log.info("=========> 规则不存在，直接返回");
            return;
        }

        //2、遍历报警规则，查询对应的设备数据
        log.info("=========> 2、遍历报警规则，查询对应的设备数据");
        ruleList.forEach(alertRule -> {
            log.info("=========> 当前规则：{}", alertRule);
            List<Object> deviceDataList = new ArrayList<>();//当前规则下的所有的设备的上报数据
            if (StrUtil.equals("-1", alertRule.getIotId())) {
                //所有设备
                List<Object> temp = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA_KEY + alertRule.getProductKey());
                if (CollUtil.isNotEmpty(temp)) {
                    deviceDataList.addAll(temp);
                }
            } else {
                //指定设备
                Object deviceData = redisTemplate.opsForHash().get(CacheConstants.IOT_DEVICE_LAST_DATA_KEY + alertRule.getProductKey(), alertRule.getIotId());
                if (ObjectUtil.isNotNull(deviceData)) {
                    deviceDataList.add(deviceData);
                }
            }

            if (CollUtil.isEmpty(deviceDataList)) {
                log.info("=========> 设备数据不存在，进入下个规则的校验");
                return;//相当于continue
            }

            //3、遍历每一个设备数据，根据报警规则进行判断
            log.info("=========> 3、遍历当前规则下的设备数据，根据报警规则进行告警判断");
            deviceDataList.forEach(deviceDataStr -> {
                // deviceDataStr 是从Redis中取出来的JSON字符串==>[{},{}]
                List<DeviceDataVo> list = JSONUtil.toList(deviceDataStr.toString(), DeviceDataVo.class);
                list.forEach(deviceDataVo -> {
                    if (StrUtil.equals(alertRule.getFunctionId(), deviceDataVo.getFunctionId())) {
                        processAlertData(deviceDataVo, alertRule);//处理设备报警数据
                    }
                });
            });

        });

    }

    /**
     * 处理设备报警数据
     *
     * @param deviceData 设备数据
     * @param alertRule  告警规则
     */
    private void processAlertData(DeviceDataVo deviceData, AlertRule alertRule) {
        log.info("=========> 3.0、当前设备数据：{}", deviceData);
        //3.1、数据上报时间超过一分钟为历史数据，放弃处理
        log.info("=========> 3.1、判断数据上报时间是否超过一分钟");
        if (ObjectUtil.isNotNull(deviceData.getAlarmTime())
                && deviceData.getAlarmTime().isBefore(LocalDateTime.now().minusMinutes(1))) {
            log.info("=========> 数据上报时间超过一分钟为历史数据，放弃处理");
            return;
        }
        //3.2、判断数据上报时间是否在 报警生效时间段范围内，不在则放弃处理  00:00:00~23:59:59
        log.info("=========> 3.2、判断数据上报时间是否在 报警生效时间段范围内");
        if (ObjectUtil.isNotNull(alertRule.getAlertEffectivePeriod())
                && ObjectUtil.isNotNull(deviceData.getAlarmTime())) {
            LocalTime start = LocalTime.parse(alertRule.getAlertEffectivePeriod().split("~")[0]);
            LocalTime end = LocalTime.parse(alertRule.getAlertEffectivePeriod().split("~")[1]);
            if (deviceData.getAlarmTime().toLocalTime().isBefore(start)
                    || deviceData.getAlarmTime().toLocalTime().isAfter(end)) {
                log.info("=========> 数据上报时间不在 报警生效时间段范围内，放弃处理");
                return;
            }
        }

        String alertTriggerCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        //3.3、判断数据是否满足报警规则，满足则发送告警消息   当前设备数据  运算符  阈值 ==>  36.8 >= 37.5
        log.info("=========> 3.3、判断数据是否满足报警规则");
        String expression = deviceData.getDataValue() + " " + alertRule.getOperator() + " " + alertRule.getValue();
        if (!evaluateExpression(expression)) {
            log.info("=========> 当前数据不满足报警规则,数据计算表达式：{}", expression);
            //删除设备的连续累计报警数
            redisTemplate.delete(alertTriggerCountKey);
            return;
        }

        //3.4、判断当前设备是否处于报警沉默周期，如果还在沉默周期，放弃处理
        log.info("=========> 3.4、判断当前设备是否处于报警沉默周期");
        String alertSilentKey = CacheConstants.ALERT_SILENT_PREFIX + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        if (Boolean.TRUE.equals(redisTemplate.hasKey(alertSilentKey))) {
            log.info("=========> 当前设备处于报警沉默周期，放弃处理");
            return;
        }
        //3.5、判断连续报警次数是否达到持续周期的阈值
        log.info("=========> 3.5、判断连续报警次数是否达到持续周期的阈值");
        Long count = redisTemplate.opsForValue().increment(alertSilentKey);//
        if (count < alertRule.getDuration()) {
            log.info("=========> 当前设备连续报警次数（{}）未达到持续周期的阈值({})，放弃处理", count, alertRule.getDuration());
            return;
        }
        //3.6、处理告警数据
        generateAlertData(deviceData, alertRule);
    }


    /**
     * 处理告警数据
     *
     * @param deviceData 设备数据
     * @param alertRule  报警规则
     */
    private void generateAlertData(DeviceDataVo deviceData, AlertRule alertRule) {
        log.info("=========> 3.6、处理告警数据");
        //1、删除Redis中连续累计报警数
        String alertTriggerCountKey = CacheConstants.ALERT_TRIGGER_COUNT_PREFIX + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        redisTemplate.delete(alertTriggerCountKey);
        log.info("=========> 3.6.1、删除Redis中连续累计报警数,key:{}", alertTriggerCountKey);
        //2、存储报警沉默周期到Redis中
        String alertSilentKey = CacheConstants.ALERT_SILENT_PREFIX + deviceData.getIotId() + ":" + deviceData.getFunctionId() + ":" + alertRule.getId();
        log.info("=========> 3.6.2、存储报警沉默周期到Redis中,key:{}", alertSilentKey);
        redisTemplate.opsForValue().set(alertSilentKey, JSONUtil.toJsonStr(deviceData), alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
        //3、存储报警数据存入数据库
        //3.1、获取告警消息的消费者【告知人】
        //存储所有的告警消息消费者的id
        Set<Long> alertConsumer = new HashSet<>();
        if (alertRule.getAlertDataType().equals(AlertDataType.ELDER.getOrdinal())) {
            // 老人异常数据 设备 ==> 关联的老人 ==> 护理人员
            if (deviceData.getLocationType().equals(LocationType.PORTABLE.getOrdinal())) {
                // 情况一： 设备的 location_type 是0 随身设备，则 binding_location 是老人ID ==> 关联的老人 ==> 护理人员
                List<Long> temp = deviceMapper.selectNurserByIotIdPortable(deviceData.getIotId());
                alertConsumer.addAll(temp);
            } else {
                // 情况二： 设备的 location_type 是1 固定设备，且physical_location_type是2 床位， 则 binding_location 是床位ID.
                List<Long> temp = deviceMapper.selectNurserByIotIdFixed(deviceData.getIotId());
                alertConsumer.addAll(temp);
            }
        } else {
            // 设备异常数据 ： 找角色名称是维修工的所有人员
            List<Long> temp = userMapper.selectUserByRoleName(deviceMaintainerRole);
            alertConsumer.addAll(temp);
        }
        List<Long> temp = userMapper.selectUserByRoleName(managerRole);
        alertConsumer.addAll(temp);

        if (CollUtil.isNotEmpty(alertConsumer)) {
            //3.2、存储数据库
            log.info("=========> 3.6.3、存储报警数据存入数据库,告知人集合{}", alertConsumer);
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警",
                    alertRule.getFunctionName(),
                    alertRule.getOperator(),
                    alertRule.getValue(),
                    alertRule.getDuration());

            AlertData alertData = BeanUtil.toBean(deviceData, AlertData.class);
            alertData.setAlertRuleId(alertRule.getId());
            alertData.setAlertReason(alertReason);
            alertData.setType(alertRule.getAlertDataType());
            //状态，0：待处理，1：已处理,
            alertData.setStatus(0);

            //循环遍历消费者id集合，每个消费者创建一个对应的alertData数据，存入数据库
            List<AlertData> list = alertConsumer.stream().map(id -> {
                AlertData dbAlertData = BeanUtil.toBean(alertData, AlertData.class);
                dbAlertData.setUserId(id);//接收人ID
                return dbAlertData;
            }).collect(Collectors.toList());

            alertDataMapper.batchInsert(list);
            log.info("=========> 3.6.4 基于WebSocket 发送告警信息弹框给对应的接收人的客户端浏览器 接收人为{} ", alertConsumer);
            AlertNotifyDto alertNotifyDto = BeanUtil.toBean(alertData, AlertNotifyDto.class);
            alertNotifyDto.setFunctionName(alertRule.getFunctionName());
            alertNotifyDto.setAlertDataType(alertData.getType());
            alertNotifyDto.setNotifyType(1);
            webSocketServer.sendMessageToConsumer(alertNotifyDto, new ArrayList<>(alertConsumer));
            log.info("=========> ####################### END ##################");
        }


    }


    private boolean evaluateExpression(String expression) {
        expression = expression.replace(" ", "");
        Pattern pattern = Pattern.compile("(-?\\d+(\\.\\d+)?)\\s*([<>]=?|==|!=)\\s*(-?\\d+(\\.\\d+)?)");
        Matcher matcher = pattern.matcher(expression);

        if (matcher.matches()) {
            double num1 = Double.parseDouble(matcher.group(1));
            String operator = matcher.group(3);
            double num2 = Double.parseDouble(matcher.group(4));

            switch (operator) {
                case ">":
                    return num1 > num2;
                case ">=":
                    return num1 >= num2;
                case "<":
                    return num1 < num2;
                case "<=":
                    return num1 <= num2;
                case "==":
                    return num1 == num2;
                case "!=":
                    return num1 != num2;
                default:
                    throw new IllegalArgumentException("Unsupported operator: " + operator);
            }
        } else {
            throw new IllegalArgumentException("Invalid expression format: " + expression);
        }
    }
}
